PROBLEM SET 1¶

Computational Methods in Economics¶

Marcelo Alonso¶

AI USE: Eu utilizei o ChatGPT para me auxiliar na organização e otimização do código, no reconhecimento da sintaxe das funções do Julia e na correção ortográfica/concordância das minhas respostas. Como é a minha primeira vez utilizando Julia, recorri à IA para aprender a linguagem.¶
In [5]:
using Pkg

# 1) Ativa o ambiente local do projeto usando o diretório onde o script está salvo (relative path)
Pkg.activate(@__DIR__)

# 2) Resolve as dependências do ambiente (confere se as versões dos pacotes são compatíveis)
Pkg.resolve()

# 3) Instancia o ambiente, instalando os pacotes conforme definido nos arquivos Project.toml e Manifest.toml
Pkg.instantiate()

# 4) Define a lista dos pacotes necessários para o projeto
packages = [
    "Plots",
    "Interpolations",
    "DataFrames",
    "DataInterpolations",
    "Random",
    "Distributions",
    "BenchmarkTools",
    "Roots"
]

# 5) Adiciona os pacotes da lista, instalando-os se ainda não estiverem presentes
Pkg.add(packages)
  Activating project at `c:\Users\Eco\Desktop\Computational Methods\Problem Set 1`
  No Changes to `C:\Users\Eco\Desktop\Computational Methods\Problem Set 1\Project.toml`
  No Changes to `C:\Users\Eco\Desktop\Computational Methods\Problem Set 1\Manifest.toml`
   Resolving package versions...
  No Changes to `C:\Users\Eco\Desktop\Computational Methods\Problem Set 1\Project.toml`
  No Changes to `C:\Users\Eco\Desktop\Computational Methods\Problem Set 1\Manifest.toml`

QUESTION 1¶

a) What is your best guess for the life expectancy at birth for someone born in 1996?¶

RESPOSTA DO ITEM A:¶

A interpolação linear e a PCHIP produzem estimativas bastante similares, mas a segunda é mais interessante uma vez que uma de suas propriedades é a preservação da monotonicidade.

In [6]:
using Plots

# ----------------------------------------------------------------------------
# 1) Criar um dicionário com os dados
dados_vida = Dict(
    "anos" => [1940, 1950, 1960, 1970, 1980, 1991, 2000, 2010],
    "expectativa" => [45.5, 48.0, 52.5, 57.6, 62.5, 66.9, 71.1, 74.4]
)

# ----------------------------------------------------------------------------
# 2) Plotar o gráfico de dispersão (scatter) dos dados
scatter(
    dados_vida["anos"], 
    dados_vida["expectativa"],
    markersize=6, markercolor=:blue,
    label="Expectativa de vida", 
    grid=true
)

# Rótulos e título (em português)
xlabel!("Ano")
ylabel!("Expectativa de Vida")
title!("Expectativa de Vida no Brasil (1940 - 2010)")
In [7]:
using Interpolations

# ----------------------------------------------------------------------------
# 1) Extrair dados do dicionário
anos = dados_vida["anos"]
expectativa_vida = dados_vida["expectativa"]

# 2) Definir o ano que desejamos estimar
ano_estimar = 1996

# 3) Interpolação Linear
itp_linear = linear_interpolation(anos, expectativa_vida, extrapolation_bc=Flat())
estimativa_linear = itp_linear(ano_estimar)

# 4) Interpolação Monotônica (PCHIP) usando FritschButland
#    *Atenção:* Este método pode falhar em dados não uniformes na sua versão atual do Interpolations.jl.
itp_pchip = interpolate(anos, expectativa_vida, FritschButlandMonotonicInterpolation())
estimativa_pchip = itp_pchip(ano_estimar)

# 5) Exibir resultados
println("Estimativa de expectativa de vida para o ano $ano_estimar:")
println("  - Interpolação Linear: ", round(estimativa_linear, digits=2))
println("  - Interpolação PCHIP : ", round(estimativa_pchip, digits=2))
Estimativa de expectativa de vida para o ano 1996:
  - Interpolação Linear: 69.23
  - Interpolação PCHIP : 69.3

b) Using any interpolation method of your choice, plot the life expectancy for all years between 1940 and 2010.¶

RESPOSTA DO ITEM B:¶

In [8]:
using Plots
using Interpolations  



# Extrair os dados do dicionário
anos = dados_vida["anos"]
expectativa = dados_vida["expectativa"]

# Criar um vetor com todos os anos de 1940 a 2010
anos_interp = collect(1940:2010)

# ----------------------------------------------------------------------------
# 1) Interpolação Linear (usando Interpolations.jl)
itp_linear = linear_interpolation(anos, expectativa, extrapolation_bc=Flat())
vida_linear = [itp_linear(x) for x in anos_interp]

# ----------------------------------------------------------------------------
# 2) Interpolação PCHIP (usando Fritsch-Butland para monotonicidade)
itp_pchip = interpolate(anos, expectativa, FritschButlandMonotonicInterpolation())
vida_pchip = [itp_pchip(x) for x in anos_interp]

# ----------------------------------------------------------------------------
# Plotar os dados originais e as curvas interpoladas
scatter(
    anos, expectativa,
    label="Dados Originais", marker=:circle,
    xlabel="Ano", ylabel="Expectativa de Vida",
    title="Interpolação Linear vs PCHIP (1940 - 2010)",
    legend=:topleft, grid=true
)

plot!(anos_interp, vida_linear, label="Linear (Interpolations.jl)", lw=2, color=:red)
plot!(anos_interp, vida_pchip, label="PCHIP (Interpolations.jl)", lw=2, color=:green)

QUESTION 2¶

a) Create a vector of trying points, x. Draw a sample with 2500 numbers using a uniform distribution over the interval [0,10]. You should keep this fixed for the entire exercise.¶

RESPOSTA DO ITEM A:¶

In [9]:
using Random, Distributions, Interpolations, Statistics, BenchmarkTools, Printf

# Dicionário para armazenar variáveis e funções
 dados_q2 = Dict(
    "sigma" => 3,        # Parâmetro σ
    "epsilon" => 1e-8,   # Parâmetro ε
    "intervalo" => (0,10),  # Intervalo de amostragem [0,10]
    "n_pontos" => 2500   # Número de pontos a serem amostrados
)

# Definir a função f(x)
function f(x, sigma, epsilon)
    return ((x + epsilon)^(1 - sigma)) / (1 - sigma)
end

# Armazenar função
 dados_q2["f"] = f

# === Item (a) ===
Random.seed!(42)
dados_q2["x"] = rand(Uniform(dados_q2["intervalo"]...), dados_q2["n_pontos"])
dados_q2["fx"] = [dados_q2["f"](x, dados_q2["sigma"], dados_q2["epsilon"]) for x in dados_q2["x"]]

println("Primeiros 10 pontos gerados e seus respectivos f(x):")
for i in 1:10
    println("x = ", round(dados_q2["x"][i], digits=4), " → f(x) = ", round(dados_q2["fx"][i], digits=4))
end
Primeiros 10 pontos gerados e seus respectivos f(x):
x = 1.7357 → f(x) = -0.166
x = 3.2166 → f(x) = -0.0483
x = 2.5859 → f(x) = -0.0748
x = 1.6644 → f(x) = -0.1805
x = 5.2702 → f(x) = -0.018
x = 4.8302 → f(x) = -0.0214
x = 3.9066 → f(x) = -0.0328
x = 8.0276 → f(x) = -0.0078
x = 9.8076 → f(x) = -0.0052
x = 0.9443 → f(x) = -0.5607

b) Let our grid be from 0 to 10, we will use n = 10 points, equally spaced. First create the grid t. Then, compute the function for all the 10 grid points, generating a y vector.¶

RESPOSTA DO ITEM B:¶

O grid $t$ foi criado com 10 pontos igualmente espaçados no intervalo $[0,10]$ usando LinRange(). Para cada ponto do grid, a função fornecida pelo enunciado foi avaliada e armazenada no vetor y.

In [10]:
# === Item (b) ===
dados_q2["grid_t"] = LinRange(dados_q2["intervalo"]..., 10)
dados_q2["y"] = [dados_q2["f"](x, dados_q2["sigma"], dados_q2["epsilon"]) for x in dados_q2["grid_t"]]

println("\nGrid t: ", dados_q2["grid_t"])
println("Valores de f(x) no grid: ", dados_q2["y"])
Grid t: LinRange{Float64}(0.0, 10.0, 10)
Valores de f(x) no grid: [-5.0e15, -0.40499999271000015, -0.10124999908875, -0.04499999973000001, -0.02531249988609375, -0.01619999994168, -0.01124999996625, -0.008265306101195337, -0.006328124985761716, -0.00499999999]

(c) For each interpolation method (linear, spline, and pchip/akima), compute the approximation $\hat{f}(x)$ for all trying points and the error $e(x) = f(x) − \hat{f}(x)$. Summarize the results showing in a table the average quadratic error for each method and the average time of execution (for all 2500 points).¶

RESPOSTA DO ITEM C:¶

Após criar os pontos iniciais, armazenados no grid $t$, usamos três técnicas diferentes de interpolação (linear, spline cúbico e PCHIP) para gerar curvas/retas que ligam esses 10 pontos.

Ao obtermos as curvas/retas interpoladas, queremos medir o desempenho delas avaliando-as nos 2500 pontos armazenados no vetor $x$ e que são introduzidos na entrada $xvals$ da função avaliar_interpolador:

  1. Entrada da função:

    • $interpolador$: Curva interpolada gerada por uma das três técnicas.
    • $xvals$: Os 2500 pontos onde avaliamos a precisão das curvas interpoladas.
    • $fvals$: Os valores exatos da função original em cada um desses 2500 pontos.
  2. Medição do tempo (@belapsed):

    • O tempo calculado por @belapsed é obtido executando repetidamente a operação $interpolador.($xvals)
    • A operação $interpolador.($xvals) avalia o valor da função na reta/curva interpolada por uma das três técnicas para todos os 2500 pontos.
    • O resultado final é o tempo médio dessas múltiplas execuções.
    • O @inbounds é utilizado para otimizar o acesso às posições dos vetores, evitando verificações desnecessárias dos limites.
  3. Cálculo do erro médio quadrático (MSE):

    • Após calcular os valores interpolados ($fx\_aprox$) para os 2500 pontos, o erro médio quadrático (MSE) é calculado pela expressão: $$ MSE = \frac{1}{n} \sum_{i=1}^{n} (fvals_i - fx\_aprox_i)^2 $$
  4. Retorno da função:

    • Uma tupla contendo o erro médio quadrático (MSE) e o tempo médio de execução.
In [11]:
# === Item (c) ===

# Função auxiliar para avaliar interpoladores
function avaliar_interpolador(interpolador, xvals, fvals)
    tempo = @belapsed @inbounds $interpolador.($xvals)
    fx_aprox = interpolador.(xvals)
    mse = mean((fvals .- fx_aprox).^2)
    return (mse = mse, tempo = tempo)
end


itp_linear = linear_interpolation(dados_q2["grid_t"], dados_q2["y"], extrapolation_bc=Flat())
itp_spline = cubic_spline_interpolation(dados_q2["grid_t"], dados_q2["y"])
itp_pchip = interpolate(dados_q2["grid_t"], dados_q2["y"], FritschButlandMonotonicInterpolation())

res_linear = avaliar_interpolador(itp_linear, dados_q2["x"], dados_q2["fx"])
res_spline = avaliar_interpolador(itp_spline, dados_q2["x"], dados_q2["fx"])
res_pchip = avaliar_interpolador(itp_pchip, dados_q2["x"], dados_q2["fx"])

dados_q2["resultados_item_c"] = Dict(
    "linear" => res_linear,
    "spline" => res_spline,
    "pchip" => res_pchip
)

println("\n===== Resultados do Item (c) =====")
println("Método  |      MSE       |  Tempo (s)")
println("-------------------------------------")

for metodo in ["linear", "spline", "pchip"]
    res = dados_q2["resultados_item_c"][metodo]
    @printf("%-7s | %12.5e | %10.8f\n", metodo, res.mse, res.tempo)
end
===== Resultados do Item (c) =====
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  9.06932e+29 | 0.00002680
spline  |  7.71642e+29 | 0.00007140
pchip   |  7.66695e+29 | 0.00017910

d) What happens when we increase our grid to n = 15? And to n = 20? And to n = 30? And to n = 50?¶

RESPOSTA DO ITEM D:¶

Ao aumentar o número de pontos no grid, naturalmente observamos uma queda no MSE. Isso ocorre pois os métodos de interpolação tem acesso a um conjunto informacional maior e, sendo assim, conseguem se aproximar mais da função verdadeira.

In [12]:
# === Item (d) ===
grid_sizes = [15, 20, 30, 50]
dados_q2["resultados_item_d"] = Dict()

for n in grid_sizes
    grid_t = LinRange(dados_q2["intervalo"]..., n)
    grid_y = [dados_q2["f"](ti, dados_q2["sigma"], dados_q2["epsilon"]) for ti in grid_t]

    itp_linear = linear_interpolation(grid_t, grid_y, extrapolation_bc=Flat())
    itp_spline = cubic_spline_interpolation(grid_t, grid_y)
    itp_pchip = interpolate(grid_t, grid_y, FritschButlandMonotonicInterpolation())

    res_linear = avaliar_interpolador(itp_linear, dados_q2["x"], dados_q2["fx"])
    res_spline = avaliar_interpolador(itp_spline, dados_q2["x"], dados_q2["fx"])
    res_pchip = avaliar_interpolador(itp_pchip, dados_q2["x"], dados_q2["fx"])

    dados_q2["resultados_item_d"][n] = Dict(
        "linear" => res_linear,
        "spline" => res_spline,
        "pchip" => res_pchip
    )
end

println("\n===== Resultados do Item (d) =====")
for n in grid_sizes
    println("\nGrid size = $n")
    println("Método  |      MSE       |  Tempo (s)")
    println("-------------------------------------")
    for metodo in ["linear", "spline", "pchip"]
        res = dados_q2["resultados_item_d"][n][metodo]
        @printf("%-7s | %12.5e | %10.8f\n", metodo, res.mse, res.tempo)
    end
end
===== Resultados do Item (d) =====

Grid size = 15
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  6.07603e+29 | 0.00002680
spline  |  5.13439e+29 | 0.00007140
pchip   |  5.09347e+29 | 0.00017920

Grid size = 20
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  4.54087e+29 | 0.00002660
spline  |  3.79128e+29 | 0.00007140
pchip   |  3.75223e+29 | 0.00017920

Grid size = 30
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  2.95235e+29 | 0.00002660
spline  |  2.45277e+29 | 0.00007130
pchip   |  2.42434e+29 | 0.00017920

Grid size = 50
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  1.70057e+29 | 0.00002660
spline  |  1.41567e+29 | 0.00007140
pchip   |  1.38375e+29 | 0.00017940

e) Now let’s repeat items b)–d) using a log-spaced grid.¶

RESPOSTA DO ITEM E:¶

Neste item, utilizamos grids com espaçamento logarítmico em vez do espaçamento linear. O grid log-spaced foi criado no intervalo $[0.0001,10]$, com tamanhos variados: $n = 10,15,20,30,50$.

A função fornecida no enunciado:

$f(x) = \frac{(x + \epsilon)^{(1 - \sigma)}}{1 - \sigma}$

possui uma curvatura acentuada perto de zero, especialmente com $\sigma=3$. Ou seja, pequenas mudanças em valores próximos de zero resultam em grandes variações no valor de $f(x)$. Ao criar um grid log-spaced, colocamos mais pontos próximos a zero, permitindo uma melhor captura dessas mudanças. Isso gera interpoladores que se ajustam mais precisamente às regiões críticas, reduzindo significativamente o erro (MSE).

Especificamente, o procedimento adotado foi:

  1. Criar um grid logarítmico com pontos igualmente espaçados no domínio logaritmo, ou seja:

    $$ \text{grid\_log} = \text{range}(\log(a), \log(b), n) $$

    em que usamos $a=0.0001$ para evitar problemas com $\log(0)$.

  2. Avaliamos a função original $f(x)$ nos pontos transformados por $\exp(t)$, isto é, utilizamos:

    $$ y_{log} = f(\exp(t), \sigma, \epsilon) $$

    Isso porque criamos o grid no domínio do logaritmo, mas a função original está definida no domínio real. Ao usar a transformação exponencial, voltamos ao domínio original.

  3. Construímos os interpoladores (linear, spline, PCHIP) no domínio logarítmico. Assim, a interpolação ocorre no espaço logarítmico, permitindo curvas mais suaves ou melhores ajustes em regiões críticas próximas a zero.

  4. Na avaliação do interpolador sobre os 2500 pontos de teste originais (xvals), transformamos cada ponto usando o logaritmo antes de avaliá-lo. A avaliação final da interpolação fica sendo:

    $$ \hat{f}(x) = interpolador(\log(x)) $$

  5. A razão de usarmos a transformação com a função $\exp(t)$ depois de criar o grid em log é para voltar ao domínio original ($[0.0001,10]$). O grid é construído no domínio do logaritmo para distribuir melhor os pontos. Quando precisamos avaliar o interpolador em um ponto do domínio original, transformamos o ponto usando $\log(x)$ antes de avaliar o interpolador.

In [13]:
# === Item (e): Log-Spaced Grid ===
using Interpolations, Statistics, BenchmarkTools, Printf

# Parâmetros já definidos no dicionário dados_q2
f       = dados_q2["f"]
sigma   = dados_q2["sigma"]
epsilon = dados_q2["epsilon"]
x_test  = dados_q2["x"]
fx_exato = dados_q2["fx"]

# Definir limites para o grid logarítmico
a = 0.0001
b = 10.0

# Tamanhos do grid logarítmico
grid_sizes_log = [10, 15, 20, 30, 50]
dados_q2["resultados_item_e"] = Dict()

# Avaliação para cada tamanho de grid
for n in grid_sizes_log
    grid_log = range(log(a), log(b), length=n)
    grid_y_log = [dados_q2["f"](exp(ti), dados_q2["sigma"], dados_q2["epsilon"]) for ti in grid_log]

    # Construir interpoladores no domínio log
    itp_linear_log = linear_interpolation(grid_log, grid_y_log, extrapolation_bc=Flat())
    itp_spline_log = cubic_spline_interpolation(grid_log, grid_y_log)
    itp_pchip_log = interpolate(grid_log, grid_y_log, FritschButlandMonotonicInterpolation())

    # Avaliar interpoladores no domínio original
    res_linear_log = avaliar_interpolador(x -> itp_linear_log(log(x)), x_test, fx_exato)
    res_spline_log = avaliar_interpolador(x -> itp_spline_log(log(x)), x_test, fx_exato)
    res_pchip_log = avaliar_interpolador(x -> itp_pchip_log(log(x)), x_test, fx_exato)

    dados_q2["resultados_item_e"] = get(dados_q2, "resultados_item_e", Dict())
    dados_q2["resultados_item_e"][n] = Dict(
        "linear" => res_linear_log,
        "spline" => res_spline_log,
        "pchip" => res_pchip_log
    )
end

# Exibir resultados organizados
println("\n===== Resultados do Item (e) - Log-Spaced Grid =====")
for n in grid_sizes_log
    println("\nGrid size (log-spaced) = $n")
    println("Método  |      MSE       |  Tempo (s)")
    println("-------------------------------------")
    for metodo in ["linear", "spline", "pchip"]
        res = dados_q2["resultados_item_e"][n][metodo]
        @printf("%-7s | %12.5e | %10.8f\n", metodo, res.mse, res.tempo)
    end
end
===== Resultados do Item (e) - Log-Spaced Grid =====

Grid size (log-spaced) = 10
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  4.61926e+08 | 0.00010810
spline  |  2.23954e+09 | 0.00016990
pchip   |  7.44634e+06 | 0.00022730

Grid size (log-spaced) = 15
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  5.42966e+07 | 0.00010790
spline  |  6.38972e+07 | 0.00016990
pchip   |  3.27027e+05 | 0.00022250

Grid size (log-spaced) = 20
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  1.39075e+07 | 0.00010820
spline  |  2.51302e+06 | 0.00016990
pchip   |  5.27969e+04 | 0.00021950

Grid size (log-spaced) = 30
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  2.01611e+06 | 0.00010810
spline  |  4.95494e+03 | 0.00017000
pchip   |  6.70282e+03 | 0.00021680

Grid size (log-spaced) = 50
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  6.55785e+04 | 0.00010810
spline  |  2.99764e-01 | 0.00016980
pchip   |  2.50489e+02 | 0.00021510

f) What conclusions can you draw from this exercise?¶

RESPOSTA DO ITEM F:¶

A precisão de um método de interpolação numérica depende da quantidade e da distribuição dos pontos no grid utilizado. Em geral, aumentar o número de pontos reduz o erro da interpolação, pois diminui o espaçamento entre eles. No entanto, grids igualmente espaçados podem ser ineficientes para funções que apresentam comportamentos acentuados em determinadas regiões do domínio, como é o caso da função analisada nesta questão, cuja inclinação se acentua significativamente à medida que se aproxima do zero. Nesses casos, faz sentido concentrar os pontos nas regiões onde o erro potencial de aproximação é maior. Para funções com comportamento singular próximo de zero, um grid distribuído em escala logarítmica é recomendado, pois concentra pontos onde a função apresenta maiores variações, reduzindo significativamente o erro quadrático médio (MSE) para todos os métodos de interpolação testados.

Entre os métodos avaliados, a interpolação PCHIP (Fritsch-Butland) demonstrou desempenho superior na maior parte dos casos, especialmente devido à sua capacidade de garantir monotonicidade, evitando oscilações artificiais nas regiões mais críticas. A interpolação spline cúbica também apresentou melhorias expressivas quando utilizada com o grid log-espaçado, destacando-se especialmente com um número maior de pontos, chegando a obter o menor MSE entre os três métodos a partir de 30 pontos.

O tempo de execução da interpolação depende mais do método escolhido do que do número de pontos utilizados no grid. A interpolação linear é a mais rápida, pois exige apenas o cálculo de uma inclinação constante entre cada par de pontos adjacentes, resultando em uma primeira derivada constante dentro de cada subintervalo. A interpolação spline cúbica demanda um tempo computacional maior por exigir o cálculo de derivadas de segunda ordem contínuas ao longo de todo o intervalo, resultando em uma aproximação mais suave e precisa, porém mais lenta. O método PCHIP, por sua vez, apresenta o maior tempo de execução entre os três métodos, já que incorpora cálculos e verificações adicionais para preservar a monotonicidade, aumentando o custo computacional.

In [14]:
using Plots

# Definir a função f(x)
sigma = 3
epsilon = 1e-8

f(x) = ((x + epsilon)^(1 - sigma)) / (1 - sigma)

# Criar os diferentes intervalos de x para os gráficos
x_vals1 = range(0, 10, length=100000)         # De 0 a 10
x_vals2 = range(0.01, 10, length=100000)  # De 0.01 a 10
x_vals3 = range(0.1, 10, length=100000)     # De 0.1 a 10
x_vals4 = range(1, 10, length=100000)       # De 1 a 10

# Criar os valores de f(x) para cada intervalo
y_vals1 = f.(x_vals1)
y_vals2 = f.(x_vals2)
y_vals3 = f.(x_vals3)
y_vals4 = f.(x_vals4)

# Criar os gráficos
p1 = plot(x_vals1, y_vals1, label="f(x)", title="x de 0 a 10", xlabel="x", ylabel="f(x)", linewidth=2)
p2 = plot(x_vals2, y_vals2, label="f(x)", title="x de 0.01 a 10", xlabel="x", ylabel="f(x)", linewidth=2)
p3 = plot(x_vals3, y_vals3, label="f(x)", title="x de 0.1 a 10", xlabel="x", ylabel="f(x)", linewidth=2)
p4 = plot(x_vals4, y_vals4, label="f(x)", title="x de 1 a 10", xlabel="x", ylabel="f(x)", linewidth=2)

# Exibir os gráficos em um grid 2x2
plot(p1, p2, p3, p4, layout=(2,2), size=(800,600))

QUESTION 3¶

a) What conclusions are different in this case?¶

RESPOSTA DO ITEM A:¶

In [15]:
using Random, Distributions, Interpolations, Statistics, BenchmarkTools, Printf

# ================
# Dicionário e Função (Questão 3)
# ================

dados_q3 = Dict(
    "k"         => 2.5,      # Parâmetro k
    "xi"        => 5.0,      # Parâmetro ξ
    "intervalo" => (1.0,10.0),# Intervalo de amostragem [1,10]
    "n_pontos"  => 2500       # Número de pontos a serem amostrados
)

# Definir a função g(x)
function g(x, k, xi)
    return 1.0 / (1.0 + exp(k * (-x + xi)))
end

# Armazenar a função no dicionário
dados_q3["g"] = g

# ================
# Item (3a)
# ================

Random.seed!(42)  # Para reprodutibilidade

# Gerar pontos x aleatórios no intervalo [1, 10]
dados_q3["x"] = rand(Uniform(dados_q3["intervalo"]...), dados_q3["n_pontos"])

# Calcular g(x) para cada ponto
dados_q3["gx"] = [dados_q3["g"](x, dados_q3["k"], dados_q3["xi"]) for x in dados_q3["x"]]

println("Primeiros 10 pontos gerados e seus respectivos g(x):")
for i in 1:10
    println("x = ", round(dados_q3["x"][i], digits=4),
            " → g(x) = ", round(dados_q3["gx"][i], digits=6))
end
Primeiros 10 pontos gerados e seus respectivos g(x):
x = 2.5622 → g(x) = 0.00225
x = 3.895 → g(x) = 0.059378
x = 3.3273 → g(x) = 0.015041
x = 2.4979 → g(x) = 0.001917
x = 5.7431 → g(x) = 0.865045
x = 5.3472 → g(x) = 0.704329
x = 4.516 → g(x) = 0.229688
x = 8.2249 → g(x) = 0.999685
x = 9.8268 → g(x) = 0.999994
x = 1.8499 → g(x) = 0.00038
In [16]:
# ================
# Item (3b)
# ================

# Construir um grid (LinRange) de 10 pontos entre [1, 10]
dados_q3["grid_t"] = LinRange(dados_q3["intervalo"]..., 10)

# Avaliar g(x) nesse grid
dados_q3["y"] = [dados_q3["g"](x, dados_q3["k"], dados_q3["xi"]) for x in dados_q3["grid_t"]]

println("\nGrid t: ", dados_q3["grid_t"])
println("Valores de g(x) no grid: ", dados_q3["y"])
Grid t: LinRange{Float64}(1.0, 10.0, 10)
Valores de g(x) no grid: [4.5397868702434395e-5, 0.0005527786369235996, 0.0066928509242848554, 0.07585818002124355, 0.5, 0.9241418199787566, 0.993307149075715, 0.9994472213630764, 0.9999546021312976, 0.9999962733607158]
In [17]:
# ================
# Item (3c)
# ================

# Função auxiliar para avaliar interpoladores
function avaliar_interpolador(interpolador, xvals, gvals)
    # tempo de avaliação via BenchmarkTools
    tempo = @belapsed @inbounds $interpolador.($xvals)
    gx_aprox = interpolador.(xvals)
    mse = mean((gvals .- gx_aprox).^2)
    return (mse = mse, tempo = tempo)
end

# Construir interpoladores no grid definido
itp_linear = linear_interpolation(dados_q3["grid_t"], dados_q3["y"], extrapolation_bc=Flat())
itp_spline = cubic_spline_interpolation(dados_q3["grid_t"], dados_q3["y"])
itp_pchip  = interpolate(dados_q3["grid_t"], dados_q3["y"], FritschButlandMonotonicInterpolation())

# Avaliar cada interpolador em todos os pontos x
res_linear = avaliar_interpolador(itp_linear, dados_q3["x"], dados_q3["gx"])
res_spline = avaliar_interpolador(itp_spline, dados_q3["x"], dados_q3["gx"])
res_pchip  = avaliar_interpolador(itp_pchip,  dados_q3["x"], dados_q3["gx"])

# Armazenar resultados
dados_q3["resultados_item_c"] = Dict(
    "linear" => res_linear,
    "spline" => res_spline,
    "pchip"  => res_pchip
)

println("\n===== Resultados do Item (c) =====")
println("Método  |      MSE       |  Tempo (s)")
println("-------------------------------------")
for metodo in ["linear", "spline", "pchip"]
    res = dados_q3["resultados_item_c"][metodo]
    @printf("%-7s | %12.5e | %10.8f\n", metodo, res.mse, res.tempo)
end
===== Resultados do Item (c) =====
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  5.06898e-04 | 0.00002660
spline  |  1.23632e-04 | 0.00007120
pchip   |  9.72989e-05 | 0.00017920
In [18]:
# ================
# Item (3d)
# ================
grid_sizes = [15, 20, 30, 50]
dados_q3["resultados_item_d"] = Dict()

for n in grid_sizes
    grid_t = LinRange(dados_q3["intervalo"]..., n)
    grid_y = [dados_q3["g"](ti, dados_q3["k"], dados_q3["xi"]) for ti in grid_t]

    itp_linear = linear_interpolation(grid_t, grid_y, extrapolation_bc=Flat())
    itp_spline = cubic_spline_interpolation(grid_t, grid_y)
    itp_pchip  = interpolate(grid_t, grid_y, FritschButlandMonotonicInterpolation())

    res_linear = avaliar_interpolador(itp_linear, dados_q3["x"], dados_q3["gx"])
    res_spline = avaliar_interpolador(itp_spline, dados_q3["x"], dados_q3["gx"])
    res_pchip  = avaliar_interpolador(itp_pchip,  dados_q3["x"], dados_q3["gx"])

    dados_q3["resultados_item_d"][n] = Dict(
        "linear" => res_linear,
        "spline" => res_spline,
        "pchip"  => res_pchip
    )
end

println("\n===== Resultados do Item (d) =====")
for n in grid_sizes
    println("\nGrid size = $n")
    println("Método  |      MSE       |  Tempo (s)")
    println("-------------------------------------")
    for metodo in ["linear", "spline", "pchip"]
        res = dados_q3["resultados_item_d"][n][metodo]
        @printf("%-7s | %12.5e | %10.8f\n", metodo, res.mse, res.tempo)
    end
end
===== Resultados do Item (d) =====

Grid size = 15
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  6.96912e-05 | 0.00002660
spline  |  1.70478e-06 | 0.00007130
pchip   |  4.99131e-06 | 0.00017920

Grid size = 20
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  2.25147e-05 | 0.00002670
spline  |  6.43664e-08 | 0.00007110
pchip   |  7.66881e-07 | 0.00017920

Grid size = 30
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  4.10513e-06 | 0.00002660
spline  |  1.18020e-09 | 0.00007130
pchip   |  5.71306e-08 | 0.00017920

Grid size = 50
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  5.25991e-07 | 0.00002660
spline  |  1.02163e-11 | 0.00007130
pchip   |  2.37676e-09 | 0.00017920
In [19]:
# ================
# Item (3e): Log-Spaced Grid
# ================
# Aqui faremos um grid logarítmico no intervalo [1, 10].

a = 1.0
b = 10.0

# Tamanhos do grid logarítmico
grid_sizes_log = [10, 15, 20, 30, 50]
dados_q3["resultados_item_e"] = Dict()

# Para facilitar, armazenamos x e g(x) exatos em variáveis
x_test   = dados_q3["x"]
gx_exato = dados_q3["gx"]

for n in grid_sizes_log
    # Criar um grid logarítmico de tamanho n, indo de ln(a) até ln(b)
    grid_log = range(log(a), log(b), length=n)
    # Avaliar g em exp(t) para t no grid_log
    grid_y_log = [g(exp(ti), dados_q3["k"], dados_q3["xi"]) for ti in grid_log]

    # Construir interpoladores no domínio log
    itp_linear_log = linear_interpolation(grid_log, grid_y_log, extrapolation_bc=Flat())
    itp_spline_log = cubic_spline_interpolation(grid_log, grid_y_log)
    itp_pchip_log  = interpolate(grid_log, grid_y_log, FritschButlandMonotonicInterpolation())

    # Avaliar no domínio original: para um x, interpolamos em log(x)
    res_linear_log = avaliar_interpolador(x -> itp_linear_log(log(x)), x_test, gx_exato)
    res_spline_log = avaliar_interpolador(x -> itp_spline_log(log(x)), x_test, gx_exato)
    res_pchip_log  = avaliar_interpolador(x -> itp_pchip_log(log(x)), x_test, gx_exato)

    dados_q3["resultados_item_e"][n] = Dict(
        "linear" => res_linear_log,
        "spline" => res_spline_log,
        "pchip"  => res_pchip_log
    )
end

println("\n===== Resultados do Item (e) - Log-Spaced Grid =====")
for n in grid_sizes_log
    println("\nGrid size (log-spaced) = $n")
    println("Método  |      MSE       |  Tempo (s)")
    println("-------------------------------------")
    for metodo in ["linear", "spline", "pchip"]
        res = dados_q3["resultados_item_e"][n][metodo]
        @printf("%-7s | %12.5e | %10.8f\n", metodo, res.mse, res.tempo)
    end
end
===== Resultados do Item (e) - Log-Spaced Grid =====

Grid size (log-spaced) = 10
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  6.29470e-04 | 0.00010520
spline  |  2.13618e-04 | 0.00016920
pchip   |  1.38986e-04 | 0.00021310

Grid size (log-spaced) = 15
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  2.26396e-04 | 0.00010520
spline  |  3.04853e-05 | 0.00016920
pchip   |  3.26497e-05 | 0.00021240

Grid size (log-spaced) = 20
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  5.70308e-05 | 0.00010530
spline  |  3.80123e-07 | 0.00016890
pchip   |  3.82886e-06 | 0.00021200

Grid size (log-spaced) = 30
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  1.24070e-05 | 0.00010520
spline  |  1.68976e-08 | 0.00016930
pchip   |  3.03789e-07 | 0.00021080

Grid size (log-spaced) = 50
Método  |      MSE       |  Tempo (s)
-------------------------------------
linear  |  1.46395e-06 | 0.00010520
spline  |  1.05564e-10 | 0.00016940
pchip   |  1.24623e-08 | 0.00021050

A principal diferença ao repetir a análise para a função $g(x) = \frac{1}{1 + e^{k(-x+\xi)}}$ é que, diferentemente da função anterior, sua inclinação não tende ao infinito à medida que nos aproximamos do início do domínio. Como consequência, a malha logarítmica deixa de trazer os mesmos benefícios no intervalo $[1,10]$, pois essa escala enfatiza regiões próximas de zero, e não há, nesse intervalo, um comportamento singular que justificaria essa escolha.

Observando os resultados numéricos, fica evidente que uma distribuição uniforme de pontos no grid, seja para um número reduzido de pontos ou para grids mais refinados, proporciona menores erros quadráticos médios (MSE).

Além disso, o spline cúbico superou os outros dois métodos a partir de 15 pontos no grid, tanto para a malha linear quanto para a logarítmica.

b) Given the shape of the function, what would be the best grid?¶

RESPOSTA DO ITEM B:¶

Para identificar onde a função $g(x) = \frac{1}{1 + e^{k(-x+\xi)}}$ varia mais rapidamente, analisamos sua derivada primeira:

$$ g'(x) = \frac{k e^{k(-x+\xi)}}{\left(1 + e^{k(-x+\xi)}\right)^2}. $$

Para localizar o ponto de inflexão, analisamos a derivada segunda, $g''(x)$, e encontramos onde ela é igual a zero (mudança de concavidade). Definindo $u = e^{k(-x+\xi)}$, temos a condição $g''(x)=0$ equivalente a encontrar o ponto onde $u=1$, resultando em:

$$ e^{k(-x+\xi)} = 1 \quad \Rightarrow \quad x = \xi. $$

Consequentemente, para melhorar a precisão da interpolação, o grid deve conter mais pontos em torno dessa região, capturando melhor o comportamento da função nessa transição.

In [20]:
using Plots

# Definir a função f(x)
k = 3
xi = 5

g(x) = 1.0 / (1.0 + exp(k * (-x + xi)))

# Criar os diferentes intervalos de x para os gráficos
x_vals1 = range(1, 10, length=100000)         # De 1 a 10
x_vals2 = range(1, 4.999, length=100000)  # De 1 a 4.999
x_vals3 = range(5, 10, length=100000)     # De 5 a 10
x_vals4 = range(4, 6, length=100000)       # De 4 a 6

# Criar os valores de f(x) para cada intervalo
y_vals1 = g.(x_vals1)
y_vals2 = g.(x_vals2)
y_vals3 = g.(x_vals3)
y_vals4 = g.(x_vals4)

# Criar os gráficos
p1 = plot(x_vals1, y_vals1, label="g(x)", title="x de 1 a 10", xlabel="x", ylabel="g(x)", linewidth=2)
p2 = plot(x_vals2, y_vals2, label="g(x)", title="x de 1 a 4.999", xlabel="x", ylabel="g(x)", linewidth=2)
p3 = plot(x_vals3, y_vals3, label="g(x)", title="x de 5 a 10", xlabel="x", ylabel="g(x)", linewidth=2)
p4 = plot(x_vals4, y_vals4, label="g(x)", title="x de 4 a 6", xlabel="x", ylabel="g(x)", linewidth=2)

# Exibir os gráficos em um grid 2x2
plot(p1, p2, p3, p4, layout=(2,2), size=(800,600))

QUESTION 4¶

a) What would be her decision if she used nearest-neighbor interpolation to decide her optimal choice?

RESPOSTA DO ITEM A:¶

In [21]:
# 1) Definindo as variáveis e dados dos amigos como um array de dicionários

# Em Julia, estamos usando Symbol como chave do dicionário (e.g: :friends; os dois pontos indicam que é símbolo) 
# e Any como valor pois podemos armazenar valores de qualquer tipo (arrays, números etc)


variaveis = Dict{Symbol, Any}()


#´variaveis´ é um dicionário e estamos definindo a chave :friends dentro de variaveis para
# armazenar um array de dicionários (cada dicionário representando um amigo).

variaveis[:friends] = [
    Dict(:nome => "A", :h => 1.0, :college => false, :wage => 1.0),
    Dict(:nome => "B", :h => 1.5, :college => false,  :wage => 1.5),
    Dict(:nome => "C", :h => 3.1, :college => true, :wage => 4.22),
    Dict(:nome => "D", :h => 4.5, :college => true,  :wage => 25.5)
]

# Human capital de Maria
variaveis[:maria_h] = 2.8



# 2) Definindo as funções em um dicionário
funcoes = Dict{Symbol, Any}()



# Função que decide via nearest-neighbor qual a escolha de Maria (college ou não)

#friends::Array{Dict{Symbol,Any}}: significa que friends deve ser um Array (vetor) 
#cujos elementos são Dict{Symbol,Any} (o mesmo formato que usamos para cada amigo).

funcoes[:nearest_neighbor_decision] = function (friends::Array{Dict{Symbol,Any}}, h::Float64) 
    min_distance = Inf # inicializa a distância mínima com ifinito
    chosen_college = nothing #  inicializa a variável que armazenará a decisão de faculdade
    
    for friend in friends   #friend vai correr por friends que é a chave que armazena o array de dicionarios
        dist = abs(friend[:h] - h)
        if dist < min_distance 
            min_distance = dist  #substitui o inf pela distância do primeiro amigo
            chosen_college = friend[:college] #olha a decisão do amigo mais próximo
        end
    end
    return chosen_college
end

# 3) Aplicando a função de nearest-neighbor para decidir se Maria deve fazer faculdade
decisao = funcoes[:nearest_neighbor_decision](variaveis[:friends], variaveis[:maria_h])
variaveis[:decisao_nn] = decisao

# 4) Exibindo o resultado
if decisao
    println("Pela interpolação do vizinho mais próximo, Maria optaria por FAZER faculdade.")
else
    println("Pela interpolação do vizinho mais próximo, Maria optaria por NÃO fazer faculdade.")
end
Pela interpolação do vizinho mais próximo, Maria optaria por FAZER faculdade.

b.1) What would be her estimate of her wage using linear interpolation?¶

RESPOSTA DO ITEM B.1:¶

In [22]:
#criando função pra interpolar linearmente

funcoes[:linear_interpolation_wage] = function(friends::Array{Dict{Symbol,Any}}, h::Float64)



    # Ordena os amigos de acordo com o capital humano (:h)
    sorted_friends = sort(friends, by = x -> x[:h]) #aqui o :h se refere ao h presente em friends que é um array de dicionários
    
    # Determina os dois amigos que circundam o valor h
    lower, upper = nothing, nothing
    n = length(sorted_friends)
    
    if h <= sorted_friends[1][:h] 
        # Extrapolação para valores abaixo do mínimo (e.g: maria tem h abaixo do min)
        lower = sorted_friends[1]
        upper = sorted_friends[2]
    elseif h >= sorted_friends[end][:h]
        # Extrapolação para valores acima do máximo (e.g: maria tem h acima do max)
        lower = sorted_friends[end-1]
        upper = sorted_friends[end]
    else
        # Encontra o intervalo em que h se encaixa
        for i in 1:n-1
            h1 = sorted_friends[i][:h]
            h2 = sorted_friends[i+1][:h]
            if h1 <= h <= h2
                lower = sorted_friends[i]
                upper = sorted_friends[i+1]
                break
            end
        end
    end
    
    # Extrai os valores de capital humano e salário dos pontos de interpolação
    h1 = lower[:h]
    h2 = upper[:h]
    wage1 = lower[:wage]
    wage2 = upper[:wage]
    
    # Fórmula da interpolação linear:
    
    wage_est = wage1 + (wage2 - wage1) * ((h - h1) / (h2 - h1))
    return wage_est
end

# -------------------------------------------------------------
# Aplicando a função para estimar o salário de Maria
salario_est = funcoes[:linear_interpolation_wage](variaveis[:friends], variaveis[:maria_h])
variaveis[:salario_est] = salario_est

println("Estima-se que o salário de Maria seja: ", round(salario_est, digits=2))
Estima-se que o salário de Maria seja: 3.71

b.2) What is her best estimate now (you can choose your interpolation method) for her wage if she stays in high school? And if she goes to college?¶

RESPOSTA DO ITEM B.2:¶

In [23]:
# Atualizamos os dados dos amigos no dicionário `variaveis` pra incluir os condicionais
variaveis[:friends] = [
    Dict(
        :nome => "A",
        :h => 1.0,
        :wage_if_HS => 1.0,
        :wage_if_college => -13.0
    ),
    Dict(
        :nome => "B",
        :h => 1.5,
        :wage_if_HS => 1.50,
        :wage_if_college => -10.50
    ),
    Dict(
        :nome => "C",
        :h => 3.1,
        :wage_if_HS => 3.10,
        :wage_if_college => 4.22
    ),
    Dict(
        :nome => "D",
        :h => 4.5,
        :wage_if_HS => 4.50,
        :wage_if_college => 25.50
    )
]



#Essa função é idêntica a do item B.1, com a diferença que incluímos um terceiro parâmetro na função
# pra diferenciar o condicional 


funcoes[:linear_interpolation_for_key] = function(
    friends::Array{Dict{Symbol,Any}},
    h::Float64,
    wage_key::Symbol
)
    # Ordena os amigos de acordo com o capital humano (:h)
    sorted_friends = sort(friends, by = x -> x[:h])
    n = length(sorted_friends)

    # Variáveis para armazenar os pontos "lower" e "upper"
    lower, upper = nothing, nothing

    # 1) Caso de extrapolação inferior
    if h <= sorted_friends[1][:h]
        lower = sorted_friends[1]
        upper = sorted_friends[2]

    # 2) Caso de extrapolação superior
    elseif h >= sorted_friends[end][:h]
        lower = sorted_friends[end-1]
        upper = sorted_friends[end]

    # 3) Caso geral (h está dentro do intervalo)
    else
        for i in 1:(n-1)
            h1 = sorted_friends[i][:h]
            h2 = sorted_friends[i+1][:h]
            if h1 <= h <= h2
                lower = sorted_friends[i]
                upper = sorted_friends[i+1]
                break
            end
        end
    end

    # Extrai os valores de 'h' e do salário específico (HS ou college)
    h1 = lower[:h]
    h2 = upper[:h]
    wage1 = lower[wage_key]
    wage2 = upper[wage_key]

    # Fórmula da interpolação linear
    wage_est = wage1 + (wage2 - wage1) * ((h - h1) / (h2 - h1))

    return wage_est
end








# Salário de Maria se ela ficar no ensino médio
salario_HS = funcoes[:linear_interpolation_for_key](
    variaveis[:friends],
    variaveis[:maria_h],
    :wage_if_HS
)

# Salário de Maria se ela for para a faculdade
salario_college = funcoes[:linear_interpolation_for_key](
    variaveis[:friends],
    variaveis[:maria_h],
    :wage_if_college
)

println("Estimativa de salário de Maria se ficar em HS: ", round(salario_HS, digits=2))
println("Estimativa de salário de Maria se for para a faculdade: ", round(salario_college, digits=2))
Estimativa de salário de Maria se ficar em HS: 2.8
Estimativa de salário de Maria se for para a faculdade: 1.46

c) Plot the two wage functions for any level of human capital in the interval [1, 4.5].¶

RESPOSTA DO ITEM C:¶

In [24]:
using Plots

# Intervalo para interpolação contínua
h_range = range(1.0, 4.5, length=100)

# Calcula os salários contínuos para cada valor de h usando a função de interpolação
wage_HS_values = [ funcoes[:linear_interpolation_for_key](variaveis[:friends], h, :wage_if_HS) for h in h_range ]
wage_college_values = [ funcoes[:linear_interpolation_for_key](variaveis[:friends], h, :wage_if_college) for h in h_range ]

# Extraindo os pontos conhecidos (dados da tabela) a partir do dicionário
friend_h = [ friend[:h] for friend in variaveis[:friends] ]
wage_HS_known = [ friend[:wage_if_HS] for friend in variaveis[:friends] ]
wage_college_known = [ friend[:wage_if_college] for friend in variaveis[:friends] ]

# Plota as funções de salário sem marcadores
plot(h_range, wage_HS_values,
     label = "Wage if HS",
     xlabel = "Human Capital (h)",
     ylabel = "Wage",
     title = "Wage Functions (HS vs College)",
     legend = :topleft,
     lw = 2)

plot!(h_range, wage_college_values,
      label = "Wage if College",
      lw = 2)

# Adiciona os pontos conhecidos provenientes da tabela
scatter!(friend_h, wage_HS_known,
         marker = (:circle,4),
         label = "HS Known Points")

scatter!(friend_h, wage_college_known,
         marker = (:diamond,4),
         label = "College Known Points")

d) Based exclusively on the college decisions and nearest-neighbor interpolation, what is the lowest level of human capital that would make Maria go to college?¶

RESPOSTA DO ITEM D:¶

O ponto de indiferença segundo o método de nearest-neighbor é a média do capital humano dos dois amigos na fronteira da decisão: B (h = 1.5, não foi para a faculdade) e C (h = 3.1, foi para a faculdade). Assim, o menor nível de capital humano que faria Maria optar por faculdade é:

$$ \frac{1.5 + 3.1}{2} = 2.3 $$

Portanto, o threshold estimado para a decisão de ir para a faculdade é h = 2.3.

e) Based on separate linear interpolations of the wage functions, what is the lowest level of human capital that would make Maria go to college?¶

RESPOSTA DO ITEM E:¶

A partir dos amigos B e C, que representam as fronteiras, realizamos interpolação linear separada em cada função:

  1. Para a função do ensino médio (HS):
    Entre B e C, como wage_if_HS cresce de 1.5 a 3.1 com h de 1.5 a 3.1, a função é linear com inclinação 1, ou seja,
    $f_{HS}(h) = h$

  2. Para a função da faculdade (College):
    Entre B e C, wage_if_college passa de -10.50 a 4.22, com h variando de 1.5 a 3.1. Assim, a inclinação é: $m = \frac{4.22 - (-10.50)}{3.1 - 1.5} = \frac{14.72}{1.6} = 9.2$

    E a equação da reta é: $f_{college}(h) = -10.50 + 9.2\,(h - 1.5)$

O ponto de indiferença (threshold) ocorre quando os salários são iguais: $f_{HS}(h) = f_{college}(h)$ Ou seja: $h = -10.50 + 9.2\,(h - 1.5)$

Resolvendo: $h = -10.50 + 9.2h - 13.8 \quad \Rightarrow \quad h = \frac{24.3}{8.2}$

Portanto, com base na interpolação linear separada das funções de salário, o menor nível de capital humano que faria Maria optar por faculdade é aproximadamente h = 2.96.

In [25]:
using Interpolations, Roots

x_points = [1.5, 3.1]
y_HS = [1.5, 3.1]
y_college = [-10.5, 4.22]

# Gera as funções interpoladas linearmente usando os pontos fornecidos
itp_HS = LinearInterpolation(x_points, y_HS)
itp_college = LinearInterpolation(x_points, y_college)


# Ela retorna a diferença entre a interpolação de HS e College em x.
f_diff(x) = itp_HS(x) - itp_college(x)

# Utiliza o método de bissecção (Bisection) para encontrar o x onde f_diff(x)=0,
# ou seja, onde as duas retas se igualam, no intervalo [1.5, 3.1].
threshold = find_zero(f_diff, (1.5, 3.1), Bisection(), atol=1e-6)

println("O ponto de indiferença é x = ", threshold)
O ponto de indiferença é x = 2.9634145140647874